package com.orkasgb.framework.zookeeper.lock.curator;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ZKCuratorLock {

    /**
     * 定义临时有序节点的父节点
     */
    private static final String LOCK_PATH = "/curator_lock";

    /**
     * 模拟库存，库存增减必须保证原子性
     */
    private static final AtomicInteger count = new AtomicInteger(2);

    /**
     * 主方法入口
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        /**
         * 定义5个线程，模拟客户端去对库存进行竞争操作。
         */
        for (int i = 0; i < 5; i++) {
            new Thread(() -> process(ZKCuratorClient.getZk()), "thread" + i).start();
            countDownLatch.countDown();
        }
    }

    /**
     * 逻辑入口
     *
     * @param client 客户端链接
     */
    private static void process(CuratorFramework client) {
            /**
             * 5个线程可以在不保证线程安全的情况下，去查询库存，
             * 有点类似抢票的时候，明明看到有余票，但是下单支付的时候，却提示无余票，支付失败。
             */
            System.out.println(Thread.currentThread().getName() + "查询库存：" + getInventory());
            tryLock(client);
    }

    /**
     * 获取当前剩余库存
     *
     * @return 剩余库存
     */
    private static int getInventory() {
        return count.get();
    }

    /**
     * 订单支付
     */
    private static void pay() {
        /**
         * 支付的时候，再次判断下是否还有余票。无余票则支付失败。
         */
        if (getInventory() <= 0) {
            System.out.println(Thread.currentThread().getName() + "库存为0，支付失败.");
            return;
        }
        System.out.println(Thread.currentThread().getName() + "库存为，" + count + "支付成功");
        /**
         * 支付成功后，库存减1
         */
        count.decrementAndGet();
    }

    private static boolean tryLock(CuratorFramework client) {
        try {
            return lock(client);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private static boolean lock(CuratorFramework client) throws Exception {
        InterProcessLock processLock = new InterProcessMutex(client, LOCK_PATH);
        try {
            if (processLock.acquire(10 * 1000, TimeUnit.SECONDS)) {
                pay();
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            unlock(processLock);
        }
        return false;
    }

    private static void unlock(InterProcessLock processLock) throws Exception {
        processLock.release();
    }
}
